/**
 * Metaphorea Visualization Library
 *
 * @author benoit.pointet
 *
 * @version beta
 *
 * Tool to apply Document Binding to Visualization Purposes.
 */

var $metaphorea = (function()
{

    // *** DEFINITIONS ***
    var mph = {
        doc : {},  // document management
        vue : {},  // display management
        api : {}   // published api
    };

    // gets local dbe pointer
    var dbe = $dbe;
    // TODO 0.3 loads full dbe script àla mootools, then point to it

    // *** mph.vue : display management

    mph.vue.target = document.documentElement;
    mph.vue.frameHeight = 300;
    mph.vue.frameWidth  = 300;

    /**
     * outputs all docs at target viewSpace
     * @returns nothing
     */
    mph.vue.setTarget = function(id) {
        mph.vue.target = document.getElementById(id);
    };


    /**
     * sets frame dimensions
     * @returns nothing
     */
    mph.vue.setFrameDimensions = function(w,h) {
        mph.vue.frameWidth  = w;
        mph.vue.frameHeight = h;
    };

    
    /**
     * sets frame dimensions
     * @returns nothing
     */
    mph.vue.Wrapper = function(iframe) {
        var cDoc = mph.vue.target.ownerDocument;
        this.wrapper = cDoc.createElement("div");
        this.wrapper.setAttribute('class','view-wrapper');
        this.header = cDoc.createElement("div");
        this.header.setAttribute('class','wrapper-header');
        // this.header.innerHTML = "DOC-NODE-NAME     @ | structure | x";
        this.wrapper.appendChild(this.header);
        this.wrapper.appendChild(iframe); // directly inject iframe
        this.footer = cDoc.createElement("div");
        this.footer.setAttribute('class','wrapper-footer');
        // this.footer.innerHTML = "legend : mapping scales";
        this.wrapper.appendChild(this.footer);
        return this.wrapper;
    };


    /**
     * dump in console a summary of an opN
     */
    mph.vue.inspectDomN = function(domN) {
        console.log("domN",domN);
        var opNs = domN.viz.opNs;
        for (var i=0;i<opNs.length;i++) {
            mph.vue.inspectOpN(opNs[i]);
        }
    }


    
    /**
     * dump in console a summary of an opN
     */
    mph.vue.inspectOpN = function(opN) {
        console.log("opN",opN.uid,opN.dblN.xmlElt,opN.getValue(),opN,opN.domN);
        opN.inOpNQs.each(function(opNQ){
            mph.vue.inspectOpNQ(opNQ);
        });
        opN.inOpBs.each(function(opB){
            mph.vue.inspectOpB(opB);
        });
    }


    mph.vue.ancestors = function (opB) {
        if (!opB.parOpBQ) {
            return opB.uid;
        }
        return mph.vue.ancestors(opB.parOpBQ.parOpB)+"("+opB.parOpBQ.uid+"["+opB.parOpBQ.dblB.get('id')+"]"+opB.uid;
    }

    
    /**
     * dump in console a summary of an opB
     */
    mph.vue.inspectOpB = function(opB) {
        opB.update();
        console.log("opB",opB.uid,opB,opB.scope,opB.dblB.xmlElt);
        // opB ancestors
        console.log("   ancestors",mph.vue.ancestors(opB));
        // scope details
        var scopestr = "";
        var bag = opB.scope.bag;
        for (var i in bag) {
            if (i !== '_latest') {
                var sum = "";
                bag[i].each(function(opN){
                    sum += opN.uid+"("+opN.getValue()+")"+",";
                });
                scopestr = scopestr + " " + i + ":" + sum;
            }
        }
        console.log("   scope",opB.scope,scopestr);
    }


    /**
     * dump in console a summary of an opB
     */
    mph.vue.inspectOpNQ = function(opNQ) {
        console.log("opNQ",opNQ.uid,opNQ,opNQ.dblN.xmlElt);
        console.log("   ancestors",mph.vue.ancestors(opNQ.parOp));
    }


    /**
     * @returns wether domN has a given class of name cName
     */
    mph.doc.hasClass = function(domN,cName) {
        if (!domN.hasAttribute) {
            return false;
        }
        var oldClass = domN.hasAttribute('class') ? domN.getAttribute('class') : '';
        if (oldClass.indexOf(cName) < 0) {
            return false;
        }
        return true;
    }


    /**
     * adds a class string to the class attribute
     * @returns nothing
     */
    mph.doc.addClass = function(domN,cName) {
        var oldClass = domN.hasAttribute('class') ? domN.getAttribute('class') : '';
        var newClass = oldClass;
        if (!mph.doc.hasClass(domN,cName)) {
            newClass = oldClass + (oldClass ? ' ' : '') + cName;
        }
        domN.setAttribute('class',newClass);
    }


    /**
     * removes a class string to the class attribute
     * @returns nothing
     */
    mph.doc.remClass = function(domN,cName) {
        var oldClass = domN.hasAttribute('class') ? domN.getAttribute('class') : '';
        var newClass = oldClass;
        if (mph.doc.hasClass(domN,cName)) {
            var cut1 = oldClass.indexOf(cName);
            var cut2 = cut1 + cName.length;
            newClass = oldClass.substr(0,cut1) + oldClass.substr(cut2,oldClass.length-1);
        }
        domN.setAttribute('class',newClass);
    }


    /**
     * switches (add/remove) a class string in the class attribute
     * @returns nothing
     */
    mph.doc.switchClass = function(domN,cName) {
        var oldClass = domN.getAttribute('class') ? domN.getAttribute('class') : '';
        if (mph.doc.hasClass(domN,cName)) {
            mph.doc.remClass(domN,cName);
        } else {
            mph.doc.addClass(domN,cName);
        }
    }


    /**
     * loads asynchronously a doc in a frame
     * @returns void
     */
    mph.adapters = {};
    mph.adapters.vizAdapter = {
        loadSrc : function(url,callback) {
            var i = window.frames.length;
            var cDoc = mph.vue.target.ownerDocument;
            var iFrame = cDoc.createElement("iframe");
            iFrame.setAttribute('name','frame'+i);
            iFrame.setAttribute('width',mph.vue.frameWidth);
            iFrame.setAttribute('height',mph.vue.frameHeight);
            mph.vue.target.appendChild(new mph.vue.Wrapper(iFrame));
            var garbage = new Date().getTime();
            garbage = (url.indexOf('?') > 0 ? "&" : "?")+ "garbage"+garbage;
            iFrame.setAttribute('src',url+garbage);
            iFrame.onload = function() {
                var win = window.frames[iFrame.name];
                var doc = win.document;
                callback(doc);
            };
        }
    }
    mph.adapters.interactionAdapter = {

        events : [],

        /**
         * performs a query described by an expr
         * with a single opN as context anchor.
         * @return an array of domNs
         */
        query : function(expr,opN) {
            var that = this;
            var domN = opN.domN;
            if (!domN.viz) {
                domN.viz = {
                    'hovered'  : false,
                    'selected' : false,
                    'focused'  : false,
                    'hidden'   : false
                };
            }
            if (expr === 'selected') {
                // add selection sensitivity
                domN.addEventListener('click',function (e) {
                    if (that.events.indexOf(e) < 0) {
                        that.events.push(e);
                        if (e.altKey) {
                            if (domN === e.target) {
                                mph.vue.inspectOpN(opN);
                            }
                        } else { // switch value
                            that._setValue(domN,expr,!domN.viz[expr]);
                        }
                    }
                },false);
            }
            if (expr === 'hovered') {
                // add hover sensitivity
                domN.addEventListener('mouseover',function (e) {
                    that._setValue(domN,expr,true);
                },false);
                domN.addEventListener('mouseout',function (e) {
                    that._setValue(domN,expr,false);
                },false);
            }
            var domNs = new $dbe.fun.Set();
            domNs.add(opN.domN);
            return domNs;
        },

        /**
         * @return the value of given opN
         */
        getValue : function(opN) {
            var domN = opN.domN;
            var p = opN.dblN.get('query');
            if (p === 'selected' || p === 'hovered') {
                return domN.viz[p];
            }
            console.log('interaction.getValue PROBLEM',opN,p);
            return '';
        },


        /**
         * sets the value of given opN to given value
         * @return nothing
         */
        setValue : function(opN,v) {
            var domN = opN.domN;
            var p = opN.dblN.get('query');
            if (v === 'true') {
                v = true;
            }
            if (v === 'false') {
                v = false;
            }            
            this._setValue(domN,p,v);
        },
        

        /**
         * sets the value of given opN to given value
         * @return nothing
         */
        _setValue : function(domN,p,v) {
            var cValue = domN.viz[p];
            if (cValue !== v) {
                if (v) {
                    domN.viz[p] = v;
                    mph.doc.addClass(domN,p);
                    mph.vue.scrollTo(domN);
                }
                if (!v) {
                    domN.viz[p] = v;
                    mph.doc.remClass(domN,p);
                }
                domN.dbeEvents.fire('DomNModified');
            }
        }

    }

    /**
    * adds a binding and resets publishing
    * @returns nothing
    */
    mph.vue.addBinding = function(url) {
        var binding = dbe.addBinding(url);
    };

    /**
    * scrolls to a given domN
    * @returns nothing
    */
    mph.vue.scrollTo = function (domN){
        var selectedPosX = 0;
        var selectedPosY = 0;
        while(domN != null){
            selectedPosX += domN.offsetLeft;
            selectedPosY += domN.offsetTop;
            domN = domN.offsetParent;
        }
        window.scrollTo(selectedPosX,selectedPosY);
    }

    // *** INITIALIZATION ***

    // hooking svg adapter to dbe
    dbe.addAdapter('svg',mph.adapters.vizAdapter);
    dbe.addAdapter('html',mph.adapters.vizAdapter);
    dbe.addAdapter('interaction',mph.adapters.interactionAdapter);
    
    // mph.api : return API object
    mph.api = {
        binding             : mph.vue.addBinding,
        setTarget           : mph.vue.setTarget,
        setFrameDimensions  : mph.vue.setFrameDimensions
    };

    return mph.api;
})();